﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using BbqFramework.Security;

namespace Bbq.Gallery
{
    /// <summary>
    /// This class allows us to store pictures in physical storage (HDD) or 
    /// use a list of Url as alternative choice. We can decide to use one or both of them.
    /// </summary>
    public class UrlPhysicalStorageGalleryProvider : PhysicalStorageGalleryProvider
    {
        #region StorageType enumeration
        /// <summary>
        /// Indicate where the pictures will be stored.
        /// </summary>
        public enum StorageTypes
        {
            /// <summary>
            /// Pictures are stored in an remote server.
            /// </summary>
            Url,
            /// <summary>
            /// Pictures are stored in local storage device.
            /// </summary>
            PhysicalStorage,
            /// <summary>
            /// Pictures are stored in both of remote server and local storage device. It's the default value.
            /// </summary>
            Mixed
        }
        #endregion

        #region Constants
        private const char SEPARATOR_PATH_URL = '\x0';
        private const string PREFIX_HTTP = "http:";
        private const string PREFIX_FTP = "ftp:";
        private const string ITEM_TITLE = "Title";
        private const string ITEM_AUTHOR = "Author";
        private const string ITEM_TOTALVOTE = "TotalVote";
        private const string ITEM_VOTECOUNT = "VoteCount";
        private const string ITEM_DATE = "Date";
        private const string ITEM_PATH = "Path";
        private const string ITEM_THUMBNAIL = "Thumbnail";
        private const string URL_DATA_FILE = "Gallery.xml";
        private const string DEFAULT_AUTHOR_NAME = "White Rose";
        #endregion

        /// <summary>
        /// Gets or sets the type of the storage. Default value is Mixed.
        /// </summary>
        /// <value>The type of the storage.</value>
        public StorageTypes StorageType { get; set; }

        #region Override functions
        /// <summary>
        /// Determines whether [is valid gallery item] [the specified file].
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns>
        /// 	<c>true</c> if [is valid gallery item] [the specified file]; otherwise, <c>false</c>.
        /// </returns>
        protected override bool IsValidGalleryItem(FileInfo file)
        {
            return string.Compare(UrlPhysicalStorageGalleryProvider.URL_DATA_FILE, file.Name,
                StringComparison.OrdinalIgnoreCase) != 0;
        }
        /// <summary>
        /// Gets the gallery items.
        /// </summary>
        /// <param name="album">The album.</param>
        /// <returns></returns>
        public override GalleryItem[] GetGalleryItems(GalleryAlbum album)
        {
            GalleryItem[] physicalItems = base.GetGalleryItems(album);
            //Load external item if it's possible
            DirectoryInfo dir = GetAlbumDirectory(album);
            XmlDocument doc = this.GetGalleryXmlDocument(dir.FullName);
            if (doc != null)
            {
                List<GalleryItem> items = new List<GalleryItem>(physicalItems);
                XmlNodeList nodeList = doc.SelectNodes("Items/Item");
                foreach (XmlNode node in nodeList)
                {
                    GalleryItem item = new GalleryItem(node[UrlPhysicalStorageGalleryProvider.ITEM_TITLE].InnerText);
                    item.AuthorName = node[UrlPhysicalStorageGalleryProvider.ITEM_AUTHOR].InnerText;
                    item.TotalVote = int.Parse(node[UrlPhysicalStorageGalleryProvider.ITEM_TOTALVOTE].InnerText);
                    item.VoteCount = int.Parse(node[UrlPhysicalStorageGalleryProvider.ITEM_VOTECOUNT].InnerText);
                    item.LastModifedDate = DateTime.Parse(node[UrlPhysicalStorageGalleryProvider.ITEM_DATE].InnerText);
                    item.CreatedDate = item.LastModifedDate;
                    item.PathFromRoot = GalleryHelper.GetPhysicalPath(album.PathFromRoot);
                    item.Url = node[UrlPhysicalStorageGalleryProvider.ITEM_PATH].InnerText;
                    item.ThumbnailUrl = node[UrlPhysicalStorageGalleryProvider.ITEM_THUMBNAIL].InnerText;
                    item.IsExternal = true;
                    items.Add(item);
                }
                return items.ToArray();
            }
            else
                return physicalItems;
        }

        /// <summary>
        /// Gets the gallery XML document.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        private XmlDocument GetGalleryXmlDocument(string path)
        {
            string dataFileName = Path.Combine(path, UrlPhysicalStorageGalleryProvider.URL_DATA_FILE);
            XmlDocument doc =new XmlDocument();
            if (File.Exists(dataFileName))  //whether we can show external pictures
            {
                doc.Load(dataFileName);
            }
            else
            {
                doc.LoadXml("<Items></Items>");
            }
            return doc;
        }

        /// <summary>
        /// Saves the gallery XML document.
        /// </summary>
        /// <param name="doc">The doc.</param>
        /// <param name="path">The path.</param>
        private void SaveGalleryXmlDocument(XmlDocument doc, string path)
        {
            string dataFileName = Path.Combine(path, UrlPhysicalStorageGalleryProvider.URL_DATA_FILE);
            doc.Save(dataFileName);
        }

        /// <summary>
        /// Gets the gallery items count.
        /// </summary>
        /// <param name="album">The album.</param>
        /// <returns></returns>
        public override int GetGalleryItemsCount(GalleryAlbum album)
        {
            int count = base.GetGalleryItemsCount(album);
            //Load external item if it's possible
            DirectoryInfo dir = GetAlbumDirectory(album);
            string dataFileName = Path.Combine(dir.FullName, UrlPhysicalStorageGalleryProvider.URL_DATA_FILE);
            if (File.Exists(dataFileName))  //whether we can show external pictures
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(dataFileName);
                XmlNodeList nodeList = doc.SelectNodes("Items/Item");
                count += nodeList.Count;
            }
            return count;
        }

        /// <summary>
        /// Gets the gallery item info.
        /// </summary>
        /// <param name="itemID">The item ID.</param>
        /// <returns></returns>
        public override GalleryItem GetGalleryItemInfo(string itemID)
        {
            if (itemID.StartsWith(PREFIX_HTTP, StringComparison.OrdinalIgnoreCase) ||
                itemID.StartsWith(PREFIX_FTP, StringComparison.OrdinalIgnoreCase))
            {
                GalleryItem item = new GalleryItem();
                int lastStar = itemID.LastIndexOf(UrlPhysicalStorageGalleryProvider.SEPARATOR_PATH_URL);
                item.PathFromRoot = itemID.Substring(lastStar + 1);
                string combinedUrl = itemID.Substring(0, lastStar);
                lastStar = combinedUrl.LastIndexOf(UrlPhysicalStorageGalleryProvider.SEPARATOR_PATH_URL);
                item.Url = combinedUrl.Substring(0, lastStar);  //URL
                if (lastStar <= combinedUrl.Length - 1)  //there is the thumnail image url
                {
                    item.ThumbnailUrl = combinedUrl.Substring(lastStar + 1);
                }
                item.IsExternal = true;
                return item;
            }
            else
                return base.GetGalleryItemInfo(itemID);
        }

        /// <summary>
        /// Resolves the item identity.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public override string ResolveItemIdentity(GalleryItem item)
        {
            if (item.IsExternal)
            {
                string combinePathUrl = string.Concat(item.Url, UrlPhysicalStorageGalleryProvider.SEPARATOR_PATH_URL,
                    item.ThumbnailUrl, UrlPhysicalStorageGalleryProvider.SEPARATOR_PATH_URL,
                    item.PathFromRoot);
                return SecureUrl.EncryptUrl(combinePathUrl);
            }
            else
                return base.ResolveItemIdentity(item);
        }

        /// <summary>
        /// Resolves the item link.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public override string ResolveItemLink(GalleryItem item)
        {
            if (item.IsExternal)
                return item.Url;    //return string.Concat("SecureUrlHandler.ashx?", SecureUrl.EncryptUrl(item.Url));
            else
                return base.ResolveItemLink(item);
        }

        /// <summary>
        /// Changes the name of the gallery item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="newItem"></param>
        public override void UpdateGalleryItem(GalleryItem item, GalleryItem newItem)
        {
            if (item.IsExternal)
            {
                XmlDocument doc = this.GetGalleryXmlDocument(item.PathFromRoot);
                XmlNode node = doc.DocumentElement.SelectSingleNode(string.Concat("Item[Path='", item.Url, "']"));
                if (node != null)
                {
                    node[UrlPhysicalStorageGalleryProvider.ITEM_TITLE].InnerText = newItem.Name;
                    this.SaveGalleryXmlDocument(doc, item.PathFromRoot);
                }
            }
            else
                base.UpdateGalleryItem(item, newItem);
        }

        /// <summary>
        /// Creates the gallery item.
        /// </summary>
        /// <param name="parentAlbum">The parent album.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public override bool CreateGalleryItem(GalleryAlbum parentAlbum, GalleryItem item)
        {
            if (item.IsExternal)
            {
                string pathFromRoot = parentAlbum.PathFromRoot;
                if (string.IsNullOrEmpty(pathFromRoot))
                    pathFromRoot = GalleryHelper.GetPhysicalPath();
                XmlDocument doc = this.GetGalleryXmlDocument(pathFromRoot);

                this.InsertOrUpdateGalleryItem(doc, item);

                this.SaveGalleryXmlDocument(doc, pathFromRoot);

                return true;
            }
            else
                return base.CreateGalleryItem(parentAlbum, item);
        }

        /// <summary>
        /// Inserts the or update gallery item.
        /// </summary>
        /// <param name="doc">The doc.</param>
        /// <param name="item">The item.</param>
        private void InsertOrUpdateGalleryItem(XmlDocument doc, GalleryItem item)
        {
            XmlNode itemNode = doc.DocumentElement.SelectSingleNode(string.Concat("Item[Path='", item.Url, "']"));
            if (itemNode == null)  //Insert
            {
                itemNode = doc.CreateElement("Item");
                XmlNode titleNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_TITLE);
                titleNode.AppendChild(doc.CreateTextNode(item.Name));
                XmlNode pathNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_PATH);
                pathNode.AppendChild(doc.CreateTextNode(item.Url));
                XmlNode thumbnailNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_THUMBNAIL);
                thumbnailNode.AppendChild(doc.CreateTextNode(item.ThumbnailUrl));
                XmlNode dateNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_DATE);
                dateNode.AppendChild(doc.CreateTextNode(item.CreatedDate.ToString("yyyy/MM/dd HH:mm:ss")));
                XmlNode totalVoteNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_TOTALVOTE);
                totalVoteNode.AppendChild(doc.CreateTextNode(item.TotalVote.ToString()));
                XmlNode voteCountNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_VOTECOUNT);
                voteCountNode.AppendChild(doc.CreateTextNode(item.VoteCount.ToString()));
                XmlNode authorNode = doc.CreateElement(UrlPhysicalStorageGalleryProvider.ITEM_AUTHOR);
                authorNode.AppendChild(doc.CreateTextNode(item.AuthorName));

                itemNode.AppendChild(titleNode);
                itemNode.AppendChild(pathNode);
                itemNode.AppendChild(thumbnailNode);
                itemNode.AppendChild(dateNode);
                itemNode.AppendChild(totalVoteNode);
                itemNode.AppendChild(voteCountNode);
                itemNode.AppendChild(authorNode);

                doc.DocumentElement.AppendChild(itemNode);
            }
            else    //Update
            {
                itemNode[UrlPhysicalStorageGalleryProvider.ITEM_TITLE].InnerText = item.Name;
            }
        }
        #endregion

        #region Import/Export function
        private XmlDocument _importXmlDoc = null;
        private string _importPathFromRoot = null;
        /// <summary>
        /// Begins the import.
        /// </summary>
        /// <param name="parentAlbum">The parent album.</param>
        public override void BeginImport(GalleryAlbum parentAlbum)
        {
            //Get the XML document
            if (string.IsNullOrEmpty(parentAlbum.PathFromRoot))
                _importPathFromRoot = GalleryHelper.GetPhysicalPath();
            else
                _importPathFromRoot = this.GetAlbumDirectory(parentAlbum).FullName;
            _importXmlDoc = this.GetGalleryXmlDocument(_importPathFromRoot);
        }

        /// <summary>
        /// Commits the import.
        /// </summary>
        public override void CommitImport()
        {
            if (this._importXmlDoc != null)
            {
                this.SaveGalleryXmlDocument(this._importXmlDoc, this._importPathFromRoot);
                this._importXmlDoc = null;
            }
        }

        /// <summary>
        /// Inserts the import entry.
        /// </summary>
        /// <param name="item">The item.</param>
        public override void InsertImportEntry(GalleryItem item)
        {
            this.InsertOrUpdateGalleryItem(this._importXmlDoc, item);
        }

        /// <summary>
        /// Clears the items.
        /// </summary>
        public override void ClearItems4Importing()
        {
            if (this._importXmlDoc != null)
                this._importXmlDoc.DocumentElement.RemoveAll();
        }
        #endregion
    }
}
